package cn.sylinx.horm.proxy.mapper.parse;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import cn.sylinx.horm.exception.HORMException;
import cn.sylinx.horm.util.ClassUtil;
import cn.sylinx.horm.util.GLog;
import cn.sylinx.horm.util.StrKit;

class DefaultMapperParser implements MapperParser {

    static final MapperParser DEFAULT_MP = new DefaultMapperParser();

    @Override
    public Namespace parse(InputStream is) {

        try {

            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder dbd = dbf.newDocumentBuilder();
            Document doc = dbd.parse(is);

            XPathFactory f = XPathFactory.newInstance();
            XPath path = f.newXPath();

            // 解析空间
            Namespace namespace = parseMapper(doc, path);

            Map<String, SqlItem> sqlItems = new HashMap<>();

            // 解析insert
            sqlItems.putAll(parseSqlItems(namespace, doc, path, SqlType.insert));
            // 解析delete
            sqlItems.putAll(parseSqlItems(namespace, doc, path, SqlType.delete));
            // 解析update
            sqlItems.putAll(parseSqlItems(namespace, doc, path, SqlType.update));
            // 解析select
            sqlItems.putAll(parseSqlItems(namespace, doc, path, SqlType.select));
            // 解析execute
            sqlItems.putAll(parseSqlItems(namespace, doc, path, SqlType.execute));

            namespace.setSqlItems(sqlItems);

            return namespace;

        } catch (Exception e) {
            GLog.error("mapper 解析错误" + e.getMessage(), e);
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    // ignore
                }
            }
        }

        return null;
    }

    private Namespace parseMapper(Document doc, XPath path) throws XPathExpressionException {
        Node mapper = (Node) path.evaluate(MapperXmlConst.MAPPER_ROOT, doc, XPathConstants.NODE);
        if (mapper == null) {
            throw new HORMException("invalid mapper");
        }

        NamedNodeMap maps = mapper.getAttributes();
        Node namespaceNode = maps.getNamedItem(MapperXmlConst.MAPPER_ROOT_NAMESPACE);
        if (namespaceNode == null) {
            throw new HORMException("namespace must be set");
        }
        String namespace = namespaceNode.getNodeValue();
        Class<?> mapperClass = getAndCheck(namespace);

        Node datasourceNode = maps.getNamedItem(MapperXmlConst.MAPPER_ROOT_DATASOURCE);
        Node dynamicNode = maps.getNamedItem("dynamic");

        Namespace n = new Namespace();
        n.setDatasource(datasourceNode == null ? "" : datasourceNode.getNodeValue());
        n.setDynamic(dynamicNode != null && "true".equalsIgnoreCase(dynamicNode.getNodeValue()));
        n.setId(namespace);
        n.setMapperClass(mapperClass);

        return n;
    }

    private Class<?> getAndCheck(String namespace) {

        try {
            Class<?> mapperClass = Class.forName(namespace, false, ClassUtil.getDefaultClassLoader());

            if (!mapperClass.isInterface()) {
                throw new HORMException("invalid mapper class");
            }

            return mapperClass;

        } catch (ClassNotFoundException e) {
            GLog.error("error " + e.getMessage(), e);
            throw new HORMException(e);
        }
    }

    private Map<String, SqlItem> parseSqlItems(Namespace namespace, Document doc, XPath path, SqlType type)
            throws XPathExpressionException {

        boolean dynamic = namespace.isDynamic();

        String exp = MapperXmlConst.MAPPER_ROOT + "/" + type.name();
        Map<String, SqlItem> maps = new HashMap<>();
        // 解析nodes
        NodeList nodes = (NodeList) path.evaluate(exp, doc, XPathConstants.NODESET);

        for (int i = 0; i < nodes.getLength(); ++i) {

            Node node = nodes.item(i);

            NamedNodeMap attrs = node.getAttributes();
            Node sqlIdNode = attrs.getNamedItem(MapperXmlConst.MAPPER_NODE_SQLID);
            if (sqlIdNode == null) {
                throw new HORMException("sql id missed");
            }
            String id = sqlIdNode.getNodeValue();
            if (StrKit.isBlank(id)) {
                throw new HORMException("sql id missed");
            }

            Node datasourceNode = attrs.getNamedItem(MapperXmlConst.MAPPER_NODE_DATASOURCE);
            String datasource = datasourceNode == null ? "" : datasourceNode.getNodeValue();

            Node retrievalNode = attrs.getNamedItem(MapperXmlConst.MAPPER_NODE_RETRIEVAL);
            Node parseSqlTypeNode = attrs.getNamedItem(MapperXmlConst.MAPPER_PARSE_SQL_TYPE);

            SqlItem sqlItem = new SqlItem();

            sqlItem.setDatasource(dynamic ? "" : datasource);
            sqlItem.setNamespaceId(namespace.getId());
            sqlItem.setRetrieval(retrievalNode == null ? (SqlType.insert == type)
                    : "true".equalsIgnoreCase(retrievalNode.getNodeValue()));
            sqlItem.setSqlId(id);
            sqlItem.setSqlType(type);
            sqlItem.setStatement(node.getTextContent().trim());
            sqlItem.setParseSqlType(parseSqlTypeNode == null ? null : parseSqlTypeNode.getTextContent().trim());

            maps.put(id, sqlItem);
        }

        return maps;
    }
}
